package com.gzc.just.play.client;

import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.gzc.just.play.Const;
import com.gzc.just.play.Utils;
import com.gzc.just.play.client.war3server.War3Server;
import com.gzc.just.play.proto.War3Proto.*;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * 负责与war3ProxyServer建立tcp连接，接收服务器指令和IP列表
 */
public class ConnectionWar3ProxyServer implements Runnable {
    private static final Logger logger = LogManager.getLogger(ConnectionWar3ProxyServer.class);

    /**
     * 服务器分配给我的war3 ip
     */
    private volatile String war3Ip;
    private volatile AsynchronousSocketChannel _clientChannel;

    @Override
    public void run() {
        try (var clientChannel = AsynchronousSocketChannel.open()) {
            // 连接到服务器
            logger.info("connect war3 proxyServer start,ip={},port={}", Client.SERVER_IP, Client.SERVER_PORT);
            clientChannel.connect(new InetSocketAddress(Client.SERVER_IP, Client.SERVER_PORT))
                    .get(Const.TIMEOUT_SECOND, TimeUnit.SECONDS);
            logger.info("connect war3 proxyServer ok,ip={},port={}", Client.SERVER_IP, Client.SERVER_PORT);
            this._clientChannel = clientChannel;

            // 发送客户端连接消息 (相当于是登录请求)
            War3Message connectMessage = War3Message.newBuilder()
                    .setType(MessageType.CLIENT_CONNECT)
                    .build();
            Utils.sendProtobufMessage(_clientChannel, connectMessage);

            while (!Thread.interrupted()) {
                ByteBuffer lengthBuffer = ByteBuffer.allocate(4);
                final int bytesRead = _clientChannel.read(lengthBuffer).get();
                if (bytesRead > 0) {
                    lengthBuffer.flip();

                    // 读取消息长度
                    final int messageLength = lengthBuffer.getInt();
                    ByteBuffer messageBuffer = ByteBuffer.allocate(messageLength);
                    // 读取消息
                    _clientChannel.read(messageBuffer).get();

                    // 解析消息
                    War3Message message = War3Message.parseFrom(messageBuffer.array());
                    // 处理消息
                    handleServerMessage(message);
                }
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    }

    private void handleServerMessage(War3Message message) throws Exception {
        switch (message.getType()) {
            case CLIENT_CONNECT_RESULT: {
                handleInitialConnection(message);
                break;
            }
            case CLIENT_LIST_UPDATE: {
                handleClientListUpdate(message);
                break;
            }
            case NOTIFICATION: {
                handleNotification();
                break;
            }
            case DATA_FORWARD: {
                handleDataForward(message);
                break;
            }
            default: {
                _clientChannel.close();
                String format = String.format("handleServerMessage error,unknown message type=%s,war3Ip=%s",
                        message.getType(), war3Ip);
                throw new RuntimeException(format);
            }
        }
    }

    private void handleInitialConnection(War3Message message) throws Exception {
        IPAssignment ipAssignment = IPAssignment.parseFrom(message.getPayload());
        war3Ip = ipAssignment.getAssignedIp();
        logger.info("receive war3 proxyServer give me war3Ip={}", war3Ip);

        for (String otherWar3Ip : ipAssignment.getOtherIpsList()) {
            if (otherWar3Ip.isEmpty()) {
                continue;
            }
            War3Server oldWar3Server = Client.connectionMap.get(otherWar3Ip);
            if (null != oldWar3Server) {
                logger.error("receive war3 proxyServer otherWar3Ip repeat,ip={}", otherWar3Ip);
                oldWar3Server.close();
            }
            logger.info("receive war3 proxyServer give me otherWar3Ip={}", otherWar3Ip);
            War3Server war3Server = new War3Server(otherWar3Ip, this);
            Client.connectionMap.put(otherWar3Ip, war3Server);
            Client.executor.submit(war3Server);
        }
    }

    private void handleClientListUpdate(War3Message message) throws Exception {
        ClientListUpdate clientListUpdate = ClientListUpdate.parseFrom(message.getPayload());

        for (String otherWar3Ip : clientListUpdate.getClientIpsList()) {
            if (!Client.connectionMap.containsKey(otherWar3Ip)) {
                logger.info("add new War3Server,otherWar3Ip={}", otherWar3Ip);
                var war3Server = new War3Server(otherWar3Ip, this);
                Client.connectionMap.put(otherWar3Ip, war3Server);
                Client.executor.submit(war3Server);
            }
        }

        for (String otherWar3Ip : Collections.list(Client.connectionMap.keys())) {
            if (clientListUpdate.getClientIpsList().contains(otherWar3Ip)) {
                continue;
            }
            // 删除不存在的war3 ip的远程客户端代理
            War3Server war3Server = Client.connectionMap.remove(otherWar3Ip);
            if (null != war3Server) {
                logger.info("remove not exist War3Server,otherWar3Ip={}", otherWar3Ip);
                war3Server.close();
            }
        }
    }

    private void handleNotification() {
        //                Notification notification = Notification.parseFrom(message.getPayload());
        // 连接服务器，提供魔兽连接
        Client.executor.submit(() -> {
            try (var connectLocalWar3Game = AsynchronousSocketChannel.open();
                 var connectProxyServer = AsynchronousSocketChannel.open()) {
                // 连接本地魔兽game
                logger.info("start tcp connect local war3 game,ip={},port={}", Client.WAR3_LOCAL_IP, Const.WAR3_PORT);
                connectLocalWar3Game.connect(new InetSocketAddress(Client.WAR3_LOCAL_IP, Const.WAR3_PORT))
                        .get(Const.TIMEOUT_SECOND, TimeUnit.SECONDS);
                logger.info("tcp connect local war3 game ok,ip={},port={}", Client.WAR3_LOCAL_IP, Const.WAR3_PORT);

                // 连接war3 proxyServer
                logger.info("start tcp connect war3ProxyServer,ip={},port={}", Client.SERVER_IP, Client.SERVER_PORT);
                connectProxyServer.connect(new InetSocketAddress(Client.SERVER_IP, Client.SERVER_PORT))
                        .get(Const.TIMEOUT_SECOND, TimeUnit.SECONDS);
                logger.info("tcp connect war3ProxyServer ok,ip={},port={}", Client.SERVER_IP, Client.SERVER_PORT);

                // 发送PROVIDE_WAR3消息
                logger.info("send PROVIDE_WAR3 message to war3ProxyServer,war3Ip={}", war3Ip);
                War3Message provideMessage = War3Message.newBuilder()
                        .setType(MessageType.PROVIDE_WAR3)
                        .setSourceIp(war3Ip)
                        .build();
                Utils.sendProtobufMessage(connectProxyServer, provideMessage);

                // 开始交换数据
                logger.info("start transfer data,connectLocalWar3Game={},connectProxyServer={}", connectLocalWar3Game, connectProxyServer);
                var taskFuture1 = Client.executor.submit(() -> Utils.transferData(connectLocalWar3Game, connectProxyServer));
                var taskFuture2 = Client.executor.submit(() -> Utils.transferData(connectProxyServer, connectLocalWar3Game));

                taskFuture1.get();
                taskFuture2.get();
                logger.info("end transfer data,connectLocalWar3Game={},connectProxyServer={}", connectLocalWar3Game, connectProxyServer);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        });
    }

    private void handleDataForward(War3Message message) {
        try {
            DataForward dataForward = DataForward.parseFrom(message.getPayload());
            String otherWar3Ip = dataForward.getFromIp();
            War3Server war3Server = Client.connectionMap.get(message.getTargetIp());
            if (null == war3Server) {
                logger.error("not found war3server,can not DATA_FORWARD,targetIp={},otherWar3Ip={}",
                        message.getTargetIp(), otherWar3Ip);
                return;
            }
            if (!dataForward.getData().isEmpty()) {
                // 直接转发原始数据
                byte[] payload = dataForward.getData().toByteArray();
                war3Server.udpSend(payload);
            }
        } catch (InvalidProtocolBufferException e) {
            logger.error("Invalid DATA_FORWARD message received", e);
        }
    }

    public void forwardUdpDataToServer(String otherWar3Ip, byte[] data, int len) {
        if (null == _clientChannel) {
            logger.error("tcp connect is null,can not send data");
            return;
        }
        if (!_clientChannel.isOpen()) {
            logger.error("tcp connect closed,can not send data");
            return;
        }

        try {
            DataForward dataForward = DataForward.newBuilder()
                    .setFromIp(otherWar3Ip)
                    .setData(ByteString.copyFrom(data, 0, len))
                    .build();
            War3Message message = War3Message.newBuilder()
                    .setType(MessageType.DATA_FORWARD)
                    .setSourceIp(otherWar3Ip)
                    .setTargetIp(war3Ip)
                    .setPayload(dataForward.toByteString())
                    .build();
            Utils.sendProtobufMessage(_clientChannel, message);
        } catch (Exception e) {
            logger.error("Failed to send UDP_RAW_DATA message", e);
        }
    }
}
